﻿#region License

// Another free, open solution by folks who get it.
// Copyright © 2011 Business in Unison, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Web.Caching;

using Patterns.SafeCode;

namespace Patterns.Cache
{
	/// <summary>
	/// 	Defines an <see cref = "ICache" /> implementation for the web.
	/// </summary>
	public class WebCache : ICache
	{
		protected readonly System.Web.Caching.Cache _cache;
		protected readonly CacheEventMessenger _messenger = new CacheEventMessenger();

		public WebCache(System.Web.Caching.Cache cache)
		{
			_cache = cache;
		}

		#region Implementation of ICache

		/// <summary>
		/// 	Gets the <see cref = "System.Object" /> with the specified key.
		/// </summary>
		public virtual object this[string key]
		{
			get { return Try.Get(() => Get<object>(key)); }
			set { Try.Do(() => Set(key, () => value)); }
		}

		/// <summary>
		/// 	Gets the item with the specified key.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "key">The key.</param>
		/// <param name = "factory">The factory to run in case a replacement item is
		/// 	needed.</param>
		/// <param name = "absoluteExpiration">The absolute expiration.</param>
		/// <param name = "slidingExpiration">The sliding expiration.</param>
		/// <returns></returns>
		/// <remarks>
		/// 	Supplying a replacement factory causes the factory's result to be
		/// 	cached before being returned.
		/// </remarks>
		public virtual T Get<T>(string key, Func<T> factory = null, DateTime? absoluteExpiration = null, TimeSpan? slidingExpiration = null) where T : class
		{
			T value = Try.Get(() => _cache.Get(key) as T);
			if (value == null && factory != null)
				Set(key, factory, absoluteExpiration, slidingExpiration);
			return value;
		}

		public virtual ICache Set<T>(string key, Func<T> factory = null, DateTime? absoluteExpiration = null, TimeSpan? slidingExpiration = null) where T : class
		{
			T value = factory();
			absoluteExpiration = absoluteExpiration ?? System.Web.Caching.Cache.NoAbsoluteExpiration;
			slidingExpiration = slidingExpiration ?? System.Web.Caching.Cache.NoSlidingExpiration;
			CacheItemRemovedCallback callback = (itemKey, item, reason) => _messenger.SendMessages(itemKey, item, CacheEventType.Remove, true);
			_cache.Insert(key, value, null, absoluteExpiration.Value, slidingExpiration.Value, CacheItemPriority.Normal, callback);
			_messenger.SendMessages(key, value, CacheEventType.Add, true);
			return this;
		}

		/// <summary>
		/// 	Removes the item with the specified key.
		/// </summary>
		/// <param name = "key">The key.</param>
		public virtual ICache Remove(string key)
		{
			object value = _cache.Remove(key);
			_messenger.SendMessages(key, value, CacheEventType.Remove, true);
			return this;
		}

		/// <summary>
		/// 	Called when an item is removed from the cache.
		/// </summary>
		/// <param name = "key">The key.</param>
		/// <param name = "action">The action.</param>
		/// <returns></returns>
		public virtual ICache OnUpdate(string key, Action<CacheEvent> action)
		{
			_messenger.AddRecipient(key, action);
			return this;
		}

		#endregion
	}
}